package cc.jq1024.config;

import cc.jq1024.annotations.DCCValue;
import com.alibaba.fastjson2.JSON;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * dcc auto config class
 * @author li--jiaqiang
 * @date 2024−07−31
 */
@Configuration("dCCAutoConfig")
@EnableConfigurationProperties(DCCAutoConfigProperties.class)
public class DCCAutoConfig implements BeanPostProcessor {

    private static final Logger log = LoggerFactory.getLogger(DCCAutoConfig.class);

    /**
     * 对象cache
     */
    private final Map<String, Object> dccObjectGroup = new HashMap<>();
    private final DCCAutoConfigProperties properties;

    private CuratorFramework zookeeperClient;

    public DCCAutoConfig(DCCAutoConfigProperties properties) throws Exception {
        this.properties = properties;
        zookeeperClient = this.createZooKeeperWithOptions(properties);
        log.info("dCCAutoConfigProperties: {}", JSON.toJSONString(properties));
    }

    @PostConstruct
    public void init() throws Exception {
        log.info("降级、熔断、限流 SDK 装配...");
        CuratorFramework zookeeperClient = this.zookeeperClient;
        // 节点判断,不存在就创建（根路径）
        if (null == zookeeperClient.checkExists().forPath(properties.getZookeeperBaseConfigPath())) {
            zookeeperClient.create().creatingParentsIfNeeded().forPath(properties.getZookeeperBaseConfigPath());
            log.info("DCC 节点监听 base node 【{}】 not absent create new done!", properties.getZookeeperBaseConfigPath());
        }
        log.info("DCC 节点监听 init done!");

        /*
         * zk固定代码：
         *
         * CuratorCache：
         * 这是Curator中的一个特性，它为ZooKeeper中的数据提供了一个缓存机制。它监听指定ZNode（ZooKeeper节点）的变化，
         * 并相应地更新缓存。这很有用，因为它允许应用程序使用本地数据副本，这可以提高性能并减少网络负载。
         *
         * CuratorCache.build(client, BASE_CONFIG_PATH_CONFIG)：是一个工厂方法，用于构建新的CuratorCache实例。
         *
         * curatorCache.start()：这个方法启动缓存。在可以使用缓存监听变化或查询数据之前，
         *                      必须调用start()方法。这个方法初始化缓存，并开始监听指定ZNode的变化。
         */
        CuratorCache curatorCache = CuratorCache.build(zookeeperClient, properties.getZookeeperBaseConfigPath());
        curatorCache.start();

        /*
         * 添加节点监听器:
         * type：这是一个枚举值，表示发生了什么类型的事件。
         *      它可以是NODE_CREATED（节点创建）、NODE_CHANGED（节点数据改变）、NODE_DELETED（节点删除）。
         * oldData：这是一个包含变化前节点数据的ChildData对象。如果事件是创建事件，这个值可能为null。
         * newData：这是一个包含变化后节点数据的ChildData对象。如果事件是删除事件，这个值可能为null
         */
        curatorCache.listenable().addListener((type, oldData, newData) -> {
            switch (type) {
                case NODE_CHANGED:
                    // 获取变化数据的path路径
                    String dccNewValuePath = newData.getPath();
                    // 获取对应的bean
                    Object newObjectBean = dccObjectGroup.get(dccNewValuePath);
                    if (null == newObjectBean) {
                        log.warn("NODE_CHANGED: newObjectBean is null, return");
                        return;
                    }
                    try {
                        Class<?> objectBeanClass = newObjectBean.getClass();
                        if (AopUtils.isAopProxy(newObjectBean)) {
                            objectBeanClass = AopUtils.getTargetClass(newObjectBean);
                        }
                        // 获取字段名
                        String filedName = dccNewValuePath.substring(dccNewValuePath.lastIndexOf("/") + 1);
                        // 获取字段
                        Field declaredField = objectBeanClass.getDeclaredField(filedName);
                        // 设置新的值
                        declaredField.setAccessible(true); // 开启访问权限
                        declaredField.set(newObjectBean, new String(newData.getData())); // 设置新值
                        declaredField.setAccessible(false); // 关闭访问权限

                        String className = newObjectBean.getClass().getSimpleName();
                        log.info("update {} filed {} 【oldData:{} -> newData:{}】 success!",className, filedName, new String(oldData.getData()), new String(newData.getData()));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                case NODE_CREATED:
                    // 获取变化数据的path路径
                    String dccOldValuePath = newData.getPath();
                    // 获取对应的bean
                    Object oldObjectBean = dccObjectGroup.get(dccOldValuePath);
                    if (null == oldObjectBean) {
                        log.warn("NODE_CREATED: oldObjectBean is null, return");
                        return;
                    }
                    try {
                        // 处理AOP代理类
                        Class<?> objectBeanClass = oldObjectBean.getClass();
                        if (AopUtils.isAopProxy(oldObjectBean))
                            objectBeanClass = AopUtils.getTargetClass(oldObjectBean);
                        // 获取字段名
                        String filedName = dccOldValuePath.substring(dccOldValuePath.lastIndexOf("/") + 1);
                        // 获取字段
                        Field declaredField = objectBeanClass.getDeclaredField(filedName);
                        // 设置新的值
                        String newFieldValue = new String(newData.getData());
                        declaredField.setAccessible(true); // 开启访问权限
                        declaredField.set(oldObjectBean, newFieldValue); // 设置新值
                        String className = oldObjectBean.getClass().getSimpleName();
                        declaredField.setAccessible(false); // 关闭访问权限
                        log.info("create {} filed {} 【data:{}】 success!",className, filedName, newFieldValue);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    break;
                case NODE_DELETED:
                    log.info("暂未实现 NODE_DELETED（节点删除）");
                    break;
                default:
                    break;
            }
        });
    }


    /**
     * redisson 配置
     * @param properties 配置文件对象
     * @return redisson bean 对象
     */
    @Bean("redissonClient")
    public RedissonClient redissonClient(DCCAutoConfigProperties properties) {
        log.info("RedisClientConfigProperties: {}", JSON.toJSONString(properties));
        Config config = new Config();
        // 根据需要可以设定编解码器；https://github.com/redisson/redisson/wiki/4.-%E6%95%B0%E6%8D%AE%E5%BA%8F%E5%88%97%E5%8C%96
        config.setCodec(JsonJacksonCodec.INSTANCE);
        config.useSingleServer()
                .setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
                .setPassword(properties.getPassword())
                .setDatabase(properties.getDatabase())
                .setConnectionPoolSize(properties.getPoolSize())
                .setConnectionMinimumIdleSize(properties.getMinIdleSize())
                .setIdleConnectionTimeout(properties.getIdleTimeout())
                .setConnectTimeout(properties.getConnectTimeout())
                .setRetryAttempts(properties.getRetryAttempts())
                .setRetryInterval(properties.getRetryInterval())
                .setPingConnectionInterval(properties.getPingInterval())
                .setKeepAlive(properties.isKeepAlive())
        ;
        RedissonClient redissonClient = Redisson.create(config);
        log.info("dcc-starter filter【redis】 initialized completed. host:{} poolSize:{} status:{}", properties.getHost(), properties.getPoolSize(), !redissonClient.isShutdown());
        return redissonClient;
    }

    /**
     * 多参数构建ZooKeeper客户端连接
     *
     * @return client
     */
    @Bean("zookeeperClient")
    public CuratorFramework createWithOptions(DCCAutoConfigProperties properties) {
        CuratorFramework client = this.zookeeperClient;
        log.info("dcc-starter component【zookeeper】 initialized completed. hosts:{} ", properties.getConnectString());
        return client;
    }
    public synchronized CuratorFramework createZooKeeperWithOptions(DCCAutoConfigProperties properties) {
        if (this.zookeeperClient == null) {
            log.info("createZooKeeperWithOptions-DCCAutoConfigProperties: {}", JSON.toJSONString(properties));
            ExponentialBackoffRetry backoffRetry = new ExponentialBackoffRetry(properties.getBaseSleepTimeMs(), properties.getMaxRetries());
            CuratorFramework client = CuratorFrameworkFactory.builder()
                    .connectString(properties.getConnectString())
                    .retryPolicy(backoffRetry)
                    .sessionTimeoutMs(properties.getSessionTimeoutMs())
                    .connectionTimeoutMs(properties.getConnectionTimeoutMs())
                    .build();
            client.start();
            return client;
        }
        return this.zookeeperClient;
    }


    /**
     * 实现 BeanPostProcessor 接口的 postProcessAfterInitialization 方法
     * 可以获取bean对象的相关方法
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return postProcessAfterInitializationHelper(bean, beanName, this.zookeeperClient, this.properties);
    }

    private Object postProcessAfterInitializationHelper(Object bean, String beanName, CuratorFramework zookeeperClient, DCCAutoConfigProperties properties) {
        // 获取bean对象的 class
        Class<?> targetBeanClass = bean.getClass();
        Object targetObjectBean = bean;
        // 处理AOP代理类
        if (AopUtils.isAopProxy(bean)) {
            targetBeanClass = AopUtils.getTargetClass(bean);
            targetObjectBean = AopProxyUtils.getSingletonTarget(bean);
        }
        // 获取对象的字段集
        Field[] declaredFields = targetBeanClass.getDeclaredFields();
        // 循环遍历 找出添加了 @DCCValue 注解的字段
        for (Field field : declaredFields) {
            if (!field.isAnnotationPresent(DCCValue.class)) continue;
            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            String value = dccValue.value();
            if (null == value || value.isEmpty()) {
                throw new RuntimeException(field.getName() + " @DCCValue is not config value config case 「isSwitch/isSwitch:1」");
            }
            // @DCCValue(value = "switch:open") -> : 后面的是默认值
            String[] splits = value.split(":");
            String key = splits[0];
            log.info(value);
            log.info(Arrays.toString(splits));
            // 获取DCCValue注解的值
            String defaultValue = splits.length == 2 ? splits[1] : null;
            // 拼接zk路径
            String keyPath = properties.getZookeeperBaseConfigPath().concat("/").concat(key);
            // 判断zk对应的路径节点是否已经有值
            try {
                if (null == zookeeperClient.checkExists().forPath(keyPath)) {
                    // 没有值，则创建路径
                    zookeeperClient.create().creatingParentsIfNeeded().forPath(keyPath);
                    // 设置默认值
                    if (null != defaultValue && !defaultValue.isEmpty()) {
                        // Java中，Field对象表示一个类的字段（成员变量）。
                        // 每个Field对象都有一个accessible属性，这个属性用来指示这个字段是否可以被访问。
                        // 默认情况下，这个属性的值取决于该字段的具体修饰符（如public, private等）：
                        //对于public字段，accessible默认为true。
                        //对于private字段，accessible默认为false。
                        field.setAccessible(true); // 开启访问权限
                        // 通过反射，设置字段值（设置哪个对象的？由参数bean指定）
                        field.set(targetObjectBean, defaultValue);
                        field.setAccessible(false); // 关闭访问权限
                        log.info("DCC 节点监听 创建节点 beanName:{} fieldName:{} defaultValue:{} keyPath:{}",beanName,field.getName(),defaultValue, keyPath);
                    }
                } else {
                    // 路径节点存在，则获取值并设置获取到的值给对应bean的字段
                    byte[] data = zookeeperClient.getData().forPath(keyPath);
                    if (null != data && data.length > 0) {
                        String configValue = new String(data);
                        field.setAccessible(true); // 开启访问权限
                        field.set(targetObjectBean, configValue);
                        field.setAccessible(false); // 关闭访问权限
                        log.info("DCC 节点监听 设置配置 beanName:{} fieldName:{} configValue:{} keyPath:{}",beanName,field.getName(),configValue, keyPath);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            // 存储bean对象，供给构造函数使用
            dccObjectGroup.put(keyPath, targetObjectBean);
        }
        return bean;
    }
}